package semantic;

import java.io.PrintStream;
import java.util.ArrayList;

import astnode.ASTNode;
import astnode.AssignStmtNode;
import astnode.BinaryOperationNode;
import astnode.BranchStmtNode;
import astnode.CallNode;
import astnode.CaseNode;
import astnode.CompoundStmtNode;
import astnode.DeclarationNode;
import astnode.ExpressionNode;
import astnode.FunctionDeclarationNode;
import astnode.IfStmtNode;
import astnode.LiteralNode;
import astnode.LoopStmtNode;
import astnode.MarkerStmtNode;
import astnode.ParameterNode;
import astnode.ReturnStmtNode;
import astnode.StmtNode;
import astnode.UnitaryOperationNode;
import astnode.VariableDeclarationNode;
import astnode.VariableNode;

import common.LogPrinter;
import common.TokenType;
import common.Visitor;

/**
 * responsible for all check except 1, 2, 7, 15-17
 * @author Yunke Li Boshi Li
 *
 */
public class SemanticVisitor implements Visitor {
	private int level = 0;
	private SymbolTable ST =null;							
	private TokenType funDecType = TokenType.VOID ;				//a flag to tell child nodes the function declaration type
	private LogPrinter logPrinter = null;
	private PrintStream errPrinter = null;
	private ArrayList<String> spellingTable = null;
	private static boolean findError = false;
	
	// Function of isInloop, isInArrayDec:
	//		isInLoop: to support checking if continue or exit appear outside loop
	//		isInArraySize: to support checking if exists a variable in size expression when declare array
	// 'isInLoop' working principle:
	//		1.Every visitor copy from parent has the same value
	//		2.When meet loop, set it true
	//		4.When meet continue, check it, conditions as follows:
	//				(1)false: outside loop => error
	//				(2)true: in loop => ignore
	private boolean isInLoop = false;
	private boolean isInArrayDec = false;
	
	// Function of meetExit, meetReturn:
	//		meetExit: to support checking if exists a Exit in loop
	//		meetReturn: to support checking if exists a Return in function
	// 'meetExit' working principle:
	//		1.It's a instance of Class
	//     2.It's created when meet loop, visitor point to it
	//		3.Every visitor copy from parent point to it, so all loop's children point to it
	//		3.when meet exit, check it, condition as follows: 
	//				(1) null: represent it is not in loop 
	//				(2) not null: represent it is in loop, so we set it true
	//		4.when leave loop, check it, conditions as follows:
	//				(1)equal true: contain 'exit'
	//				(2)equal false: does not contain 'exit'
	private Flag meetExit = null;
		
	public SemanticVisitor( SymbolTable ST, LogPrinter log, PrintStream errWriter, ArrayList<String> spellingTable ){
		this.ST = ST;
		this.logPrinter = log;
		this.errPrinter = errWriter;
		this.spellingTable = spellingTable;
	}

	public SemanticVisitor(SemanticVisitor scopeVisitor) {
		this.level = scopeVisitor.level;
		this.ST = scopeVisitor.ST;
		this.funDecType = scopeVisitor.funDecType;
		this.logPrinter = scopeVisitor.logPrinter;
		this.errPrinter = scopeVisitor.errPrinter;
		this.isInLoop = scopeVisitor.isInLoop;
		this.meetExit = scopeVisitor.meetExit;
		this.isInArrayDec = scopeVisitor.isInArrayDec;
		this.spellingTable = scopeVisitor.spellingTable;
	}

	/**
	 * @param operator operator of binary operation
	 * @return true => left operand and right operand are both bool
	 * 				 false => left operand and right operand are both int
	 */
	private boolean isLogicOperator(TokenType operator){
		return operator == TokenType.OR || operator == TokenType.ORELSE|| operator == TokenType.AND|| operator == TokenType.ANDTHEN;
	}
	
	private boolean isRelationOperator( TokenType operator ){
		return operator == TokenType.GT || operator == TokenType.GTEQ || operator == TokenType.LT || operator == TokenType.LTEQ || 
				   operator == TokenType.EQ || operator == TokenType.NEQ;
	}
	
	/**
	 * get lexeme for spelling table via index
	 * @param lex
	 * @return
	 */
	private String getLexeme( int lex ){
		return spellingTable.get(lex);
	}
	
	@Override
	public void visit(ASTNode node) {	
		if( node instanceof VariableDeclarationNode ){
			visitVariableDeclarationNode( (VariableDeclarationNode) node );
		}else if( node instanceof FunctionDeclarationNode ){
			visitFunctionDeclarationNode( (FunctionDeclarationNode) node );
		}else if( node instanceof LiteralNode ){
			visitLiteralNode( (LiteralNode) node );
		}else if( node instanceof BinaryOperationNode ){
			visitBinaryOperationNode( (BinaryOperationNode) node );
		}else if( node instanceof UnitaryOperationNode ){
			visitUnitaryOperationNode( (UnitaryOperationNode) node );
		}else if( node instanceof VariableNode ){
			visitVariableNode( (VariableNode) node );
		}else if( node instanceof CallNode ){
			visitCallNode( (CallNode) node );
		}else if( node instanceof BranchStmtNode ){
			visitBranchStmtNode( (BranchStmtNode) node );
		}else if( node instanceof CompoundStmtNode ){
			visitComspoundStmtNode( (CompoundStmtNode) node );
		}else if( node instanceof MarkerStmtNode ){
			visitMarkerStmtNode( (MarkerStmtNode) node );
		}else if( node instanceof ReturnStmtNode ){
			visitReturnStmtNode( (ReturnStmtNode) node );
		}else if( node instanceof IfStmtNode ){
			visitIfStmtNode( (IfStmtNode) node );
		}else if( node instanceof LoopStmtNode ){
			visitLoopStmtNode( (LoopStmtNode) node );
		}else if( node instanceof CaseNode ){
			visitCaseNode( (CaseNode) node );
		}else if( node instanceof ParameterNode ){
			visitParameterNode( (ParameterNode) node );
		}else if( node instanceof AssignStmtNode ){
			visitAssignStmtNode( (AssignStmtNode) node );
		}
	}
	
	/**
	 * @return a new visitor copy from itself
	 */
	public 	SemanticVisitor newVisitor(){
		SemanticVisitor s = new SemanticVisitor( this );
		return s;
	}
	
	@Override
	public void visitAssignStmtNode(AssignStmtNode node) {
		printLog( node.getLineNum() , "Entering Assign Statement Node" );
		//package a variable leftVar
		//note: Promise left variable is a L-exp is a task of Parser, so we don't need to check it
		VariableNode leftVar = new VariableNode( node.getLineNum() );
		ExpressionNode rightExp = node.getRightvalue();
		leftVar.setIdentifier(node.getIdentifier() );
		leftVar.setIndex( node.getIndex() );
		node.setLeftValue( leftVar );
		//traversal left variable and right expression
		printLog( node.getLineNum() , "traversal left expression");
		leftVar.accept( newVisitor() );
		printLog(node.getLineNum() , "traversal right expression");
		rightExp.accept( newVisitor() );
		
		if( leftVar.getDeclaration() instanceof VariableDeclarationNode ){
			printLog( node.getLineNum() , "set left expression(Variable) hasInit" );
			( (VariableDeclarationNode)leftVar.getDeclaration() ).setHasInit(true);
		}
				
		if( rightExp instanceof VariableNode &&  ((VariableNode) rightExp).getDeclaration() instanceof VariableDeclarationNode ){
			VariableDeclarationNode dec =  ( (VariableDeclarationNode)((VariableNode) rightExp).getDeclaration() ) ;
			if( !dec.hasInited() ){
				//if right variable has not been initialized, we should report a error
				printErr( node.getLineNum() , 
							  String.format( "Variable must be initialized before use. Variable : %s " , getLexeme( node.getIdentifier() ) ) );
				printLog( node.getLineNum() , 
						  String.format( "Variable must be initialized before use. Variable : %s " , getLexeme( node.getIdentifier() ) ) );
				dec.setHasInit(true);
			}
		}
		//check if type match in assign statement
		if( !typeMatch( leftVar.getType() , rightExp.getType() ) ){
			//condition: Type does not match in assign statement
			printErr( node.getLineNum() ,"Type can not be matched in Assignstmt. " +
					"Left value is " + node.getLeftValue().getType() + " type, but right expression is " + node.getRightvalue().getType() +"type" );
			printLog( node.getLineNum() , "Type can not be matched in Assignstmt. " +
					"Left value is " + node.getLeftValue().getType() + " type, but right expression is " + node.getRightvalue().getType() +"type" );
	
		}

		printLog( node.getLineNum() ,  "Leaving Assign Statement Node "+ getLexeme( node.getIdentifier() ) );
	}

	@Override
	public void visitParameterNode(ParameterNode node) {
		printLog(node.getLineNum() ,"Entering Parameter Node: " + getLexeme( node.getIdentifier() ) );
		//create a next level declaration in ST, since function is declared this level
		level++;
		printLog(node.getLineNum() ,"create declaration entry");
		ST.createEntry( level, node, node.getIdentifier(), node.getLineNum() );
		
		printLog(node.getLineNum() , "Leaving Parameter Node: " + getLexeme( node.getIdentifier() ) );
	}

	@Override
	public void visitCaseNode(CaseNode node){
		printLog(node.getLineNum() , "Entering Case Node: " );
		//traversal statement
		SemanticVisitor v = newVisitor();
		printLog(node.getLineNum() ,"travesal statement");
		node.getStmt().accept( v );		
		printLog(node.getLineNum() , "Leaving Case Node: " );
	}

	@Override
	public void visitLoopStmtNode(LoopStmtNode node) {
		printLog(node.getLineNum() , "Entering Loop Node: " );
		//meet loop, set isInLoop true
		isInLoop = true;
		meetExit = new Flag(false);
		
		ArrayList<StmtNode> stmts = node.getStatements();
		printLog( node.getLineNum() , "travesal statements");
		for( int i = 0 ;i < stmts.size(); i++ ){
			stmts.get(i).accept( newVisitor() );
		}
		
		//check item 19
		if( meetExit.equals(false) ){
			printLog(node.getLineNum() ,"Warning: there is no exit in loop");
			printErr(node.getLineNum(),"Warning: there is no exit in loop");
		}
		printLog(node.getLineNum() , "Leaving Loop Node: " );
	}

	@Override
	public void visitIfStmtNode(IfStmtNode node) {
		printLog(node.getLineNum() , "Entering If Statement");
		ExpressionNode condition = node.getCondition();
		printLog( node.getLineNum() , "travesal conditions");
		condition.accept( newVisitor() );
		SemanticVisitor ifVisitor = newVisitor();
		node.getTrueBody().accept( ifVisitor );
		printLog( node.getLineNum() , "travesal if body");
		StmtNode s = node.getElseBody();
		
		if( s!=null ){
			printLog( node.getLineNum() , "travesal else body");
			SemanticVisitor elseVisitor = newVisitor();
			s.accept( elseVisitor );			
		}
				
		//check item 13
		if( !typeMatch( condition.getType() , TokenType.BOOL ) )
		{		
			//error report
			printErr( node.getLineNum() , "The expression in if's condition is not 'BOOL' type");
			printLog(node.getLineNum() , "The expression in if's condition is not 'BOOL' type");
		}
		printLog(node.getLineNum() ,"Leaving If Statement");
	}

	@Override
	public void visitReturnStmtNode(ReturnStmtNode node) {	
		printLog(node.getLineNum() ,"Entering return statement");
		if( node.getValue()!=null ){
			//the return stmt will return sth.
			//traversal it
			printLog( node.getLineNum() , "travesal value Expression:");
			node.getValue().accept( newVisitor());

			TokenType type = node.getValue().getType();
			//check item 5
			if( funDecType!=TokenType.VOID ){
				//check item 5: legitimate type part
				//means return sth. and declaration is not VOID
				if( !typeMatch( funDecType, type) ){ 
					//error return type didin't match 
					printErr( node.getLineNum() , "Return type isn't match, return value is "+node.getValue().getType()
							+ " type, but declaration is " + funDecType +" type");
					printLog(node.getLineNum() ,"Return type isn't match, return value is "+node.getValue().getType()
							+ " type, but declaration is " + funDecType +" type");
					//change declaration of funDecType, maybe don't need, we just need to replace in expression
					if( node.getValue() instanceof VariableNode ){
						DeclarationNode dec = ((VariableNode)node.getValue()).getDeclaration();
						if( dec instanceof VariableDeclarationNode ){
							VariableDeclarationNode var = (VariableDeclarationNode)dec;
							if( !var.hasInited() ){
								printErr( node.getLineNum(), String.format("variable has not been initialized",  spellingTable.get( var.getIdentifier() )));
							}
						}
					}
				}
			}else{
				//error: can not return a value since declaration of function is VOID type
				printErr( node.getLineNum() , "Can not return value since declaration of function is VOID type ");
				printLog(node.getLineNum() ,"Can not return value since declaration of function is VOID type ");
			}			
		}else{
			//check item 5: exist return part
			//the return stmt return nothing
			if( funDecType!=TokenType.VOID ){
				//error need return a value 
				printErr( node.getLineNum() , "Need return a value of "+funDecType+" type");
				printLog(node.getLineNum() ,"Need return a value of "+funDecType+" type");
			}
		}
		printLog(node.getLineNum() , "Leaving Return Statement");
	}

	@Override
	public void visitMarkerStmtNode(MarkerStmtNode node) {
		printLog(node.getLineNum() , "Entering " + node.getType() );
		TokenType type =  node.getType();
		//check item 18:
		if( (type==TokenType.CONTINUE || type==TokenType.EXIT ) ){
			if( isInLoop == false ){
				printLog(node.getLineNum() ,type+" can only appear in loop");
				printErr( node.getLineNum() , type+" can only appear in loop");
			}else{
				if( type==TokenType.EXIT ){
					meetExit.set(true);
				}
			}			
		}	
		printLog(node.getLineNum() , "Leaving" + node.getType() );
	}

	@Override
	public void visitComspoundStmtNode(CompoundStmtNode node) {
		printLog(node.getLineNum() , "Entering Compound Statement");
		//when in
		level++;
		printLog( node.getLineNum() , "travesal declarations:");
		ArrayList<DeclarationNode> decs = node.getDeclarations();
		for( int i=0 ; i<decs.size(); i++ ){
			decs.get(i).accept(newVisitor());
		}
		printLog( node.getLineNum() , "travesal statements:");
		ArrayList<StmtNode> stmts = node.getStmtNode();
		for( int i=0 ; i<stmts.size(); i++ ){
			stmts.get(i).accept(newVisitor());
		}
		for(DeclarationNode dec : decs){
			//if variable has not been used
			if( !(((VariableDeclarationNode)dec).hasInited()) ){
				printLog( dec.getLineNum() , "Variable has not been used" );
				printErr( dec.getLineNum() , "Variable has not been used");
			}
		}
		//when leave
		ST.pop(level);
		printLog(node.getLineNum() , "Leaving Compound Statement");
	}

	@Override
	public void visitBranchStmtNode(BranchStmtNode node) {
		printLog(node.getLineNum() , "Entering Branch Statement");
		printLog( node.getLineNum() , "travesal expression:");
		node.getExp().accept( newVisitor() );
				
		printLog( node.getLineNum() , "travesal cases:");
		ArrayList<CaseNode> cases  = node.getCases();
		for( int i=0 ; i<cases.size(); i++ ){
			SemanticVisitor v = newVisitor();
			cases.get(i).accept(v);
		}
		printLog(node.getLineNum() , "Leaving Branch Statement");
	}

	@Override
	public void visitCallNode(CallNode node) {
		printLog(node.getLineNum() , "Entering Call Node"+ getLexeme( node.getIdentifier() ));
		
		//previous checking if the function is environment function
		String functionName = spellingTable.get( node.getIdentifier() );
		if( 	functionName.equals("writeint") ){
			if(  node.getArguements().size()==1 ){
				node.getArguements().get(0).accept( newVisitor() ); 
				if( node.getArguements().get(0).getType() == TokenType.INT)  {
					node.setType(TokenType.VOID);
				}else{
					printErr( node.getLineNum(), "function writeint can only get one arguement whose type is INT");
				}
			}else{
				printErr( node.getLineNum(), "function writeint can only get one arguement whose type is INT");
			}
			return;
		}
		
		if( 	functionName.equals("writebool") ){
			if(  node.getArguements().size()==1  ) {
				node.getArguements().get(0).accept( newVisitor() ); 
				if( node.getArguements().get(0).getType()==TokenType.BOOL ){
					node.setType(TokenType.VOID);
				}else{
					printErr( node.getLineNum(), "function writebool can only get one arguement whose type is BOOL");
				}				
			}else{
				printErr( node.getLineNum(), "function writebool can only get one arguement whose type is BOOL");
			}		
			return;
		}
		
		if( 	functionName.equals("readbool") ){
			if(  node.getArguements().size()==0  ) {
				node.setType(TokenType.BOOL);
			}else{
				printErr( node.getLineNum(), "function readbool can not have any arguement");
			}		
			return;
		}
		
		if( 	functionName.equals("readint") ){
			if(  node.getArguements().size()==0  ) {
				node.setType(TokenType.INT);
			}else{
				printErr( node.getLineNum(), "function readint can not have any arguement");
			}		
			return;
		}
		
		
		//visit itself
		DeclarationNode n = ST.getDec( node.getIdentifier(), node.getLineNum() );
		node.setDeclaration( n );
		if(n!=null){
			node.setType( n.getType() );
		}else{
			node.setType( TokenType.UNIVERSAL );
		}
		
		//visit children
		printLog( node.getLineNum() , "travesal arguements:");
		ArrayList<ExpressionNode> args = node.getArguements();
		for( int i =0 ; i<args.size(); i++ ){
			args.get(i).accept( newVisitor() );
		}
		
		//check if the call function
		if( n instanceof FunctionDeclarationNode  ){
			//condition: call function
			//check item 6: number part
			int paramsNum = ((FunctionDeclarationNode) n).getParameterList().size();
			int argsNum = args.size();
			//check: if parameter number is same as args' number
			if( paramsNum==argsNum ){
				//check item 6: respective types part
				ArrayList<ParameterNode> params = ((FunctionDeclarationNode) n).getParameterList();
				for( int i=0; i<paramsNum;i++ ){
					ParameterNode param = params.get(i);
					ExpressionNode arg = args.get(i);
					//check: if parameter type is same as argument
					if( typeMatch( param.getType(), arg.getType() ) ){
						//check item 8
						if( param.isRef() && !(arg instanceof VariableNode) ){
							//the call of a ref param should be a variable
							printErr( node.getLineNum() , "Argument "+i+" should be a L-exp in funtion call " + getLexeme( node.getIdentifier() ) );
							logPrinter.println("Argument "+i+" should be a L-exp in funtion call " + getLexeme( node.getIdentifier() ) );
						}else if( param.isArray() ){
							if( arg instanceof VariableNode && ((VariableNode)arg).getIndex()!=null ){
								printErr( node.getLineNum(), String.format("Argument %d should not has a index", i));
								logPrinter.println( String.format("Argument %d should not has a index", i));
							}
						}
					}else{
						//error param type not compatible with arg
						printErr( node.getLineNum() , "Argument "+i+"'s type is "+arg.getType()+
								" but parameter type is "+ param.getType() + " in funtion call " + getLexeme( node.getIdentifier() ) );
						logPrinter.println("Argument "+i+"'s type is "+arg.getType()+
								" but parameter type is "+ param.getType() + " in funtion call " + getLexeme ( node.getIdentifier() ) );
					}
				}
			}else{
				//error params not compatible
				printErr( node.getLineNum() , "Arguments' number is not same as parameters in funtion call " + getLexeme( node.getIdentifier() ) );
				logPrinter.println("Arguments' number is not same as parameters in funtion call " + getLexeme( node.getIdentifier() ) );
			}
		}else{
			//condition: not a function declaration 
			printErr( node.getLineNum() , "Can not call "+ getLexeme( node.getIdentifier() ) + " since it's not a function");
			logPrinter.println("Can not call "+ getLexeme( node.getIdentifier() ) + " since it's not a function");
		}
		
		//check item 11: call part
		if( isInArrayDec == true ){
			printErr( node.getLineNum() , "Can not exist call when declare array");
		}
		printLog(node.getLineNum() , "Leaving Call Node" +  getLexeme( node.getIdentifier() ) );
	}

	@Override
	public void visitVariableNode( VariableNode node) {
		printLog(node.getLineNum() , "Entering Variable Node" +  getLexeme( node.getIdentifier() ) );
		DeclarationNode n = ST.getDec( node.getIdentifier(), node.getLineNum() );
		node.setDeclaration( n );
		if( n!= null ){
			//condition: node is declared
			printLog(node.getLineNum() , "set variable node type " + n.getType() );
			node.setType( n.getType() );
		}else{
			//condition: node is undeclared
			printLog(node.getLineNum() , "set variable node type " + TokenType.UNIVERSAL );
			node.setType(TokenType.UNIVERSAL);
		}
		
		ExpressionNode index = node.getIndex();
		if( index!=null){
			//visit index
			index.accept( newVisitor() );
			if(!typeMatch( index.getType() , TokenType.INT )){
				//error report
				printErr( node.getLineNum() , "Index of variable is " + index.getType() + " type. It can only be INT type");
				printLog(node.getLineNum() , "Index of variable is " + index.getType() + " type. It can only be INT type");
			}
		}
		
		//check item 11: variable part
		//check if variables appear in array size 
		if( isInArrayDec ==true ){
			printErr( node.getLineNum() , "When declared array, the size of array can't contain variable, it can only detemine before execute. ");
			printLog(node.getLineNum() ,"When declared array, the size of array can't contain variable, it can only detemine before execute. ");	
		}
		printLog(node.getLineNum() , "Leaving Variable Node" +  getLexeme( node.getIdentifier() ) );
	}

	@Override
	public void visitUnitaryOperationNode(UnitaryOperationNode node) {
		printLog(node.getLineNum() , "Entering Unitary Operation " + node.getType() );
		//traversal children
		ExpressionNode operand = node.getOperand();
		printLog( node.getLineNum() , "travesal operand:");
		operand.accept( newVisitor() );
		TokenType operandType = null;
		if( node.getOperator() == TokenType.NOT ){
			operandType = TokenType.BOOL;
		}else{
			operandType = TokenType.INT;
		}
		
		if( operand instanceof VariableNode && ((VariableNode) operand).getDeclaration() instanceof VariableDeclarationNode ){
			VariableDeclarationNode dec = (VariableDeclarationNode)( ((VariableNode) operand).getDeclaration() );
			if( !dec.hasInited() ){
				//if right variable has not been initialized, we should report a error
				printErr( node.getLineNum() , 
							  String.format( "Variable must be initialized before use. Variable : %s " , getLexeme( ( (VariableNode) operand ).getIdentifier() ) ) );
				printLog(node.getLineNum() , 
						String.format( "Variable must be initialized before use. Variable : %s " , getLexeme( ( (VariableNode) operand ).getIdentifier() ) ) );
				dec.setHasInit(true);
			}
		}
		
		//check item 9: unitary operation part
		//check if operand type is INT
		if( !typeMatch( operand.getType() , operandType) ){
			//condition: operand type is not INT
			printErr( node.getLineNum() , "The operand variable of '-' is "+operand.getType()+" .It can only be "+operandType+" type");
			printLog(node.getLineNum() , "The operand variable of '-' is "+operand.getType()+" .It can only be "+operandType+" type");
			//check item 14:Unitary Part
			//check: operand type
			if( operand instanceof VariableNode ){
					//condition: variable node
					//to avoid next error, set UNIVERSAL
					DeclarationNode dec = ((VariableNode) operand).getDeclaration();
					dec.setType( TokenType.UNIVERSAL );		
			}else if( operand instanceof CallNode ){ 
				//check item 3: unitary operation part
				//condition: call node
				CallNode call = (CallNode)operand;
				if( operand.getType() == TokenType.VOID ){
					//error the void function can only appear in call stmt not allowed in expressions
					printErr( node.getLineNum() , "Function "+ getLexeme( call.getIdentifier() ) + " can only appear in call stmt");
					printLog(node.getLineNum() , "Function "+ getLexeme( call.getIdentifier() ) + " can only appear in call stmt");
				}
			}
		}
		
		printLog(node.getLineNum() , "Leaving Unitary Operation " + node.getType() );
	}

	@Override
	public void visitBinaryOperationNode(BinaryOperationNode node) {
		printLog(node.getLineNum() ,  "Entering Binary Operation Node" + node.getType() );
		//traversal children
		printLog( node.getLineNum() , "travesal left expression:");
		ExpressionNode leftExp = node.getLeftExp();
		printLog( node.getLineNum() , "travesal right expression:");
		ExpressionNode rightExp = node.getRightExp();		
		leftExp.accept( newVisitor() );
		rightExp.accept( newVisitor() );
		
		if( leftExp instanceof VariableNode && ((VariableNode) leftExp).getDeclaration() instanceof VariableDeclarationNode){
			VariableDeclarationNode dec = (VariableDeclarationNode)( ((VariableNode) leftExp).getDeclaration() );
			if( !dec.hasInited() ){
				//if right variable has not been initialized, we should report a error
				printErr( node.getLineNum() , 
							  String.format( "Variable must be initialized before use. Variable : %s " , getLexeme( ( (VariableNode) leftExp ).getIdentifier() ) ) );
				printLog( node.getLineNum() , 
						  String.format( "Variable must be initialized before use. Variable : %s " , getLexeme( ( (VariableNode) leftExp ).getIdentifier() ) ) );
				dec.setHasInit(true);
			}
		}
		
		if( rightExp instanceof VariableNode && ((VariableNode) rightExp).getDeclaration() instanceof VariableDeclarationNode ){
			VariableDeclarationNode dec = (VariableDeclarationNode)( ((VariableNode) rightExp).getDeclaration() );
			if( !dec.hasInited() ){
				//if right variable has not been initialized, we should report a error
				printErr( node.getLineNum() , 
							  String.format( "Variable must be initialized before use. Variable : %s " , getLexeme( ( (VariableNode) rightExp ).getIdentifier() ) ) );
				dec.setHasInit(true);
			}
		}
		
		//check item 9: binary operation part
		//check: operand type
		if( isLogicOperator( node.getOperator() ) || (leftExp.getType() == TokenType.BOOL && isRelationOperator( node.getOperator() ) )){
			//both bool
			//check if left exp is bool type
			if( !typeMatch( leftExp.getType() , TokenType.BOOL )){
				//condition: not bool type=> error
				//check item 3: binary operation part, note: a function declared void will be regard as a expression declared VOID
				printErr( node.getLineNum() , "Left expression of "+node.getOperator()+" is "+leftExp.getType() + " type, it must be BOOL type");
				printLog( node.getLineNum() , "Left expression of "+node.getOperator()+" is "+leftExp.getType() + " type, it must be BOOL type");
				//check item 14: Binary Part
				if( leftExp instanceof VariableNode ){
					//condition: variable=>error=>set UNIVERSAL
					printLog( node.getLineNum() , "set left expression UNIVERSAL");
					((VariableNode) leftExp).getDeclaration().setType(TokenType.UNIVERSAL);
				}
					
			}
			if(!typeMatch( rightExp.getType() , TokenType.BOOL)){
				printErr( node.getLineNum() , "Right expression of "+node.getOperator()+" is "+rightExp.getType() + " type, it must be BOOL type");
				printLog( node.getLineNum() , "Right expression of "+node.getOperator()+" is "+rightExp.getType() + " type, it must be BOOL type");
				//check item 14: Binary Part
				if( rightExp instanceof VariableNode ){
					printLog( node.getLineNum() ,  "set right Expression(Variable) type UNIVERSAL");
					((VariableNode) rightExp).getDeclaration().setType(TokenType.UNIVERSAL);
				}
			}
		}else{
			//both int
			if( !typeMatch( leftExp.getType() , TokenType.INT )){
				printErr( node.getLineNum() , "Left expression of "+node.getOperator()+" is "+leftExp.getType() + " type, it must be INT type");
				printLog( node.getLineNum() , "Left expression of "+node.getOperator()+" is "+leftExp.getType() + " type, it must be INT type");
				//check item 14: Binary Part
				if( leftExp instanceof VariableNode ){
					printLog( node.getLineNum() , "set right expression(Variable) type UNIVERSAL");
					((VariableNode) leftExp).getDeclaration().setType(TokenType.UNIVERSAL);
				}
			}
			if(!typeMatch( rightExp.getType() , TokenType.INT )){
				printErr( node.getLineNum() , "Right expression of "+node.getOperator()+" is "+rightExp.getType() + " type, it must be INT type");
				printLog( node.getLineNum() , "Right expression of "+node.getOperator()+" is "+rightExp.getType() + " type, it must be INT type");
				//check item 14: Binary Part
				if( rightExp instanceof VariableNode ){
					printLog( node.getLineNum() , "set right expression(Variable) type UNIVERSAL");
					((VariableNode) rightExp).getDeclaration().setType(TokenType.UNIVERSAL);
				}
			}			
		}	
		
		printLog(node.getLineNum() ,  "Leaving Binary Operation Node" + node.getType() );
	}

	@Override
	public void visitLiteralNode(LiteralNode node) {
		
	}

	@Override
	public void visitVariableDeclarationNode(VariableDeclarationNode node) {
		//create declaration
		printLog( node.getLineNum() , "Entering Variable Declaration Node" + getLexeme( node.getIdentifier() ) );
		printLog( node.getLineNum() , "create declaration in ST");
		ST.createEntry(level, node, node.getIdentifier(), node.getLineNum() );	
		//traversal children
		printLog( node.getLineNum() , "traversal follows");
		ArrayList<VariableDeclarationNode> follows = node.getFollowList();
		for( int i=0 ; i<follows.size() ; i++ ){
			follows.get(i).accept(newVisitor());
		}
		
		ExpressionNode size = node.getSize();
		if( size!=null ){
			isInArrayDec = true;
			//traversal size
			size.accept( newVisitor() );
			//check item 10
			if( !typeMatch( size.getType() , TokenType.INT ) ){
				printErr( node.getLineNum() , "The size of array is "+size.getType() + " type, it can only be INT type");
				printLog( node.getLineNum() , "The size of array is "+size.getType() + " type, it can only be INT type");
				
			}
		}
		printLog( node.getLineNum() , "Leaving Variable Declaration Node" + getLexeme( node.getIdentifier() ) );
	}

	@Override
	public void visitFunctionDeclarationNode(FunctionDeclarationNode node) {
		printLog( node.getLineNum() , "Entering Function Declaration Node" + getLexeme( node.getIdentifier() ) );
		
		funDecType = node.getType();
		//create flag
		
		printLog( node.getLineNum() , "traversal params");
		ArrayList<ParameterNode> params = node.getParameterList();
		for( int i=0; i<params.size() ; i++ ){
			params.get(i).accept( newVisitor() );
		}
				
		node.getComspond().accept( newVisitor() );
		
		printLog( node.getLineNum() , "traversal statements");
		ArrayList<StmtNode> stmts = node.getComspond().getStmtNode();
		StmtNode lastStmt = stmts.get( stmts.size()-1) ;
		//check item: 4
		if( funDecType!=TokenType.VOID  && !(lastStmt instanceof ReturnStmtNode)  ){
			printErr(  node.getLineNum() , "Last statement of this fuction must be a return function " + getLexeme( node.getIdentifier() ) );
			printLog( node.getIdentifier() , "Last statement in fuction must be a return function " + getLexeme( node.getIdentifier() ) );
		}
		
		printLog( node.getLineNum() , "Leaving Variable Declaration Node" + getLexeme( node.getIdentifier() ) );
	}


	private boolean typeMatch( TokenType type1, TokenType type2){
		return type2 == TokenType.UNIVERSAL || type1 == TokenType.UNIVERSAL || type1 == type2  ;
	}
	
	private void printErr( int lineNum , String err ){
		findError = true;
		errPrinter.println( String.format("line %d: %s", lineNum, err) );
	}
	
	private void printLog( int lineNum , String log ){
		logPrinter.println( String.format("line %d: %s", lineNum, log) );
	}
	
	public static boolean containErr(){
		return findError;
	}
}
